﻿//Monitor.xaml.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Xaml
{
	using System;
	using System.Globalization;
	using System.Linq;
	using System.Windows.Data;
	using System.Collections.Generic;

	using Ecng.Collections;
	using Ecng.Serialization;
	using Ecng.Xaml;

	using Hardcodet.Wpf.GenericTreeView;

	using StockSharp.Logging;
	using StockSharp.Algo;
	using StockSharp.Algo.Strategies;

	/// <summary>
	/// Компонент для мониторинга работы торговых стратегий.
	/// </summary>
	public partial class Monitor : ILogListener, IPersistable
	{
		private readonly Dictionary<ILogSource, Tuple<ThreadSafeObservableCollection<LogMessage>, LogSourceNode>> _logInfo = new Dictionary<ILogSource, Tuple<ThreadSafeObservableCollection<LogMessage>, LogSourceNode>>();

		/// <summary>
		/// Создать <see cref="Monitor"/>.
		/// </summary>
		public Monitor()
		{
			InitializeComponent();

			_logInfo.Add(_sourcesTree.StrategyRootNode.Source, new Tuple<ThreadSafeObservableCollection<LogMessage>, LogSourceNode>(new ThreadSafeObservableCollection<LogMessage>(), _sourcesTree.StrategyRootNode));
			_logInfo.Add(_sourcesTree.CoreRootNode.Source, new Tuple<ThreadSafeObservableCollection<LogMessage>, LogSourceNode>(new ThreadSafeObservableCollection<LogMessage>(), _sourcesTree.CoreRootNode));

			_sourcesTree.SelectedItem = _sourcesTree.StrategyRootNode;

			_sourcesTree.NodeAdded += OnSourcesTreeNodeAdded;
			_sourcesTree.NodeRemoved += OnSourcesTreeNodeRemoved;
		}

		/// <summary>
		/// Графический компонент для отображения логов.
		/// </summary>
		public LogControl LogControl { get { return _logControl; } }

		private void OnSourcesTreeNodeAdded(LogSourceNode node)
		{
			_logInfo.Add(node.Source, new Tuple<ThreadSafeObservableCollection<LogMessage>, LogSourceNode>(new ThreadSafeObservableCollection<LogMessage>(), node));
		}

		private void OnSourcesTreeNodeRemoved(LogSourceNode node)
		{
			foreach (var childNode in node.ChildNodes)
				OnSourcesTreeNodeRemoved(childNode);

			_logInfo.Remove(node.Source);
		}

		/// <summary>
		/// Записать сообщение.
		/// </summary>
		/// <param name="message">Отладочное сообщение.</param>
		public virtual void WriteMessage(LogMessage message)
		{
			if (message == null)
				throw new ArgumentNullException("message");

			WriteMessages(message.Source, new[] { message });
		}

		/// <summary>
		/// Записать сообщения.
		/// </summary>
		/// <param name="messages">Отладочные сообщения.</param>
		public virtual void WriteMessages(IEnumerable<LogMessage> messages)
		{
			if (messages == null)
				throw new ArgumentNullException("messages");
			
			messages.GroupBy(m => m.Source).ForEach(g => WriteMessages(g.Key, g));
		}

		private void AddNodeIfNeeded(ILogSource source)
		{
			if (_logInfo.ContainsKey(source))
				return;

			// если новый источник данных
			var newSources = new Stack<ILogSource>();
			newSources.Push(source);

			var root = source.Parent;

			// ищем корневую ноду, которая уже была ранее добавлена
			while (root != null && !_logInfo.ContainsKey(root))
			{
				newSources.Push(root);
				root = root.Parent;
			}

			LogSourceNode rootNode;

			// если корневой ноды нет, то создаем ее, основываясь на корневом источнике
			if (root == null)
			{
				root = newSources.Peek();
				rootNode = (root is Strategy) ? _sourcesTree.StrategyRootNode : _sourcesTree.CoreRootNode;
			}
			else
				rootNode = _logInfo[root].Item2;

			// добавляем поочередно ноды для новых источников
			foreach (var newSource in newSources)
			{
				// при добавлении ноды с источником, автоматически добавляются и все его дочерние источник
				// но если источник указал в качестве родителя другой источник, который не добавил его в ILogSource.Childs,
				// то будет ошибка. поэтому проверяем каждый раз

				var info = _logInfo.TryGetValue(newSource);

				if (info == null)
				{
					var sourceNode = new LogSourceNode(newSource, _sourcesTree);
					rootNode.ChildNodes.Add(sourceNode);
					rootNode = sourceNode;
				}
				else
					rootNode = info.Item2;
			}
		}

		private void WriteMessages(ILogSource source, IEnumerable<LogMessage> messages)
		{
			AddNodeIfNeeded(source);

			// записываем одно и то же сообщение в родительские ноды
			// чтобы просматривать через родительский источник как его сообщение, так и всех дочерних источников
			var currentNode = _logInfo[source].Item2;
			while (currentNode != null)
			{
				var list = _logInfo[currentNode.Source].Item1;

				list.AddRange(messages);

				if (list.Count > 2 * _logControl.MaxItemsCount && _logControl.MaxItemsCount > 0)
					list.RemoveRange(0, list.Count - _logControl.MaxItemsCount);

				currentNode = currentNode.ParentNode;
			}
		}

		private void OnSelectedItemChanged(object sender, RoutedTreeItemEventArgs<LogSourceNode> e)
		{
			_logControl.Messages = e.NewItem != null 
				? _logInfo[e.NewItem.Source].Item1 
				: new ThreadSafeObservableCollection<LogMessage>();
		}

		#region Implementation of IPersistable

		void IPersistable.Load(SettingsStorage storage)
		{
			storage.SetValue("LogControl", _logControl.Save());
		}

		void IPersistable.Save(SettingsStorage storage)
		{
			var settings = storage.GetValue<SettingsStorage>("LogControl");
			if(settings!= null)
				((IPersistable)_logControl).Load(settings);
		}

		#endregion
	}

	class SourceStateConverter : IValueConverter
	{
		object IValueConverter.Convert(object value, Type targetType, object parameter, CultureInfo culture)
		{
			var strategy = value as Strategy;

			if (strategy == null)
				return -1;

			switch (strategy.ProcessState)
			{
				case ProcessStates.Started:
					return 0;
				case ProcessStates.Stopping:
					return 1;
				case ProcessStates.Stopped:
					return 2;
				default:
					return -1;
			}
		}

		object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
		{
			var num = (int)value;

			switch (num)
			{
				case 0:
					return ProcessStates.Started;
				case 1:
					return ProcessStates.Stopping;
				case 2:
					return ProcessStates.Stopped;
				default:
					return null;
			}
		}
	}
}